Packages

library(XML)
library(ggplot2)
library(magrittr)
library(yaml)
library(lubridate)
library(RCurl)
library(readxl)
library(BSDA)
library(forecast)
library(caret)
library(RSQLite)
library(psych)

———————————– IMPORTING DATA ————————————

Importing global temperature (by city) data set. This data set was found on Kaggle: https://www.kaggle.com/berkeleyearth/climate-change-earth-surface-temperature-data

temps.by.city <- read.csv("GlobalLandTemperaturesByCity.csv")
names(temps.by.city) <- c("Date", "AverageTemperature",
                                "AverageTemperatureUncertainty", "City", 
                                "Country", "Latitude", "Longitude")

Importing atmospheric data from the web. Some background information about the data: Spline fits to the Law Dome firn and ice core records and the Cape Grim record. October 2008. The spline fits follow Enting (1987) and attenuate variations with periods of less than 20 years by 50%. See Etheridge et al., JGR, 1996; Etheridge et al., JGR, 1998; MacFarling Meure et al., GRL, 2006; contact david.etheridge@csiro.au

Column 1: Year AD Column 2: CH4 Spline (ppb) Column 3: Growth Rate (ppb/yr) Column 4: NOAA04 scale Column 5: Year AD Column 6: CO2 Spline (ppm) Column 7: Growth Rate (ppm/yr) Column 8: Year AD Column 9: N2O Spline (ppb) Column 10: Growth Rate (ppm/yr)

url <- "ftp://ftp.ncdc.noaa.gov/pub/data/paleo/icecore/antarctica/law/law2006.txt"
gas.data <- read.table(url, skip = 182, header = TRUE, nrows = 2004)
gas.data <- gas.data[c(-5, -8)] # removing duplicate columns
names(gas.data) <- c("YearAD", "CH4spl", "CH4_GrRt", "NOAA04", "CO2spl", "CO2_GrRt", "N2Ospl", "N2O_GrRt")

Importing human population data from UN report found at https://www.un.org/esa/population/publications/sixbillion/sixbilpart1.pdf. Data scraped from page 5 of the PDF report.

ā€œTabulaā€ is open-source application I used to scrape data from PDF (http://tabula.technology/). Tabula application scrapes the PDF and exports data in .csv file format. I will import the .csv file into R after saving to my working directory.

human.pop <- read.csv("tabula-sixbilpart1.csv", header = TRUE, skip = 1)
names(human.pop) <- c("Year", "PopulationInBillions")

First, functions to help determine outliers in the data sets. Functions to determine Z-Score (# of standard deviations from mean)

# Creating a helper function to perform z-score standardization on an element of a vector
zScore_helper <- function(x, v) {
    # Rescale an element "x" in vector "v" based on number of standard deviations from the mean
    # Args: x is an element of a numeric vector
    #       v is a numeric vector
    # Returns: a vector containing unbounded numeric values, of the same length as "v"       
    return((x - mean(v)) / sd(v)) # z-score standardization
}
# Wrapper function for zScore: takes in a vector and standardizes it
zScore <- function(vector) {
    # Rescale all elements of a numeric vector based on their z-score
    # Args: vector is a numeric vector
    # Returns: numeric vector containing unbounded numeric values, of the same length as "v"
    if (any(is.na(vector))) { # if there are any NAs in the vector
        vector <- vector[-which(is.na(vector))] # remove all NAs
    }
    return(sapply(vector, zScore_helper, v = vector)) # determine z-score of each element in vector
}

Data exploration and visualization:

————————— ATMOSPHERIC GAS CONCENTRATIONS / HUMAN POPULATION ———————————- Atmospheric Gas concentrations over time:

recent.gas.data <- subset(gas.data, YearAD > 1750)
ggplot(gas.data, aes(YearAD)) + 
  geom_line(aes(y = CH4spl, color = "CH4")) +
  geom_line(aes(y = CO2spl, color = "CO2")) +
    geom_line(aes(y = N2Ospl, color = "N2O")) +
    labs(x = "Year", y = "Concentration (PPM)", title = "Atmospheric Gas Concentrations since 0 AD")

ggplot(gas.data, aes(YearAD)) + 
  geom_line(aes(y = CO2spl, color = "CO2")) +
    geom_line(aes(y = N2Ospl, color = "N2O")) +
    labs(x = "Year", y = "Concentration (PPM)", title = "Atmospheric Gas Concentrations since 0 AD")

ggplot(recent.gas.data, aes(YearAD)) + 
  geom_line(aes(y = CH4spl, color = "CH4")) +
  geom_line(aes(y = CO2spl, color = "CO2")) +
    geom_line(aes(y = N2Ospl, color = "N2O")) +
    labs(x = "Year", y = "Concentration (PPM)", title = "Atmospheric Gas Concentrations since 1750")

ggplot(recent.gas.data, aes(YearAD)) + 
  geom_line(aes(y = CO2spl, color = "CO2")) +
    geom_line(aes(y = N2Ospl, color = "N2O")) +
    labs(x = "Year", y = "Concentration (PPM)", title = "Atmospheric Gas Concentrations since 1750")

We note that there is a drastic increase in the levels of each gas in teh last ~250 years. Levels of CH4 have increased more rapidly than CO2 and N2O.

Human Population over time

ggplot(human.pop, aes(Year)) + 
  geom_line(aes(y = PopulationInBillions, color = "Population in Billions")) +
    labs(x = "Year", y = "Human Population (billions)", title = "Human Population since 0 AD")

Human population has also skyrocketed in recent years. The graph closely resembles the atmospheric gas concentrations plots from above.

Histograms: outlier detection and normality:

hist(gas.data$CH4spl)

hist(gas.data$CO2spl)

hist(gas.data$N2Ospl)

hist(gas.data$CH4_GrRt)

hist(gas.data$CO2_GrRt)

hist(gas.data$N2O_GrRt)

hist(human.pop$PopulationInBillions)

Based on the histograms above, there are likely outliers for each of the variables above (most of the graphs skew towards the left side). However, drawing from domain knowledge, many of the recent spike in atmospheric concentrations of gases such as CO2 and N2O can be attributed to human activity. Therefore, the recent (last ~250 years) exponential growth in human population could be a cause of the apparently exponential rise in atmospheric gas concentration. Because the purpose of this project is in part to quantify the effects of these increases, I will try to use a log transform to better normalize the data.

Histograms of log transforms:

hist(log(gas.data$CH4spl))

hist(log(gas.data$CO2spl))

hist(log(gas.data$N2Ospl))

hist(log(gas.data$CH4_GrRt))
NaNs produced

hist(log(gas.data$CO2_GrRt))
NaNs produced

hist(log(gas.data$N2O_GrRt))
NaNs produced

hist(log(human.pop$PopulationInBillions))

The histograms with log transform do not show a marked increase from those without the transform. While the data does not appear to perfectly follow a normal distribution, I am hesistant to disregard the exceptionally high concentrations in recent years as outliers. There is a clear upward trend over time, and I think it is more practical to view these high readings as the result of human activity in recent years.

While I do not plan on removing outliers because of this reasoning, we will identify all instances that fall farther than 3 standard deviations from the mean (theoretical ā€œoutliersā€).

Determining how many elements from each feature are theoretical outliers

outlier.years <- c(which(abs(zScore(gas.data$CH4spl)) > 3),
                   which(abs(zScore(gas.data$CH4_GrRt)) > 3),
                   which(abs(zScore(gas.data$N2Ospl)) > 3),
                   which(abs(zScore(gas.data$NOAA04)) > 3),
                   which(abs(zScore(gas.data$N2O_GrRt)) > 3),
                   which(abs(zScore(gas.data$CO2spl)) > 3),
                   which(abs(zScore(gas.data$CO2_GrRt)) > 3))
outlier.years <- unique(outlier.years)
outlier.years <- outlier.years[order(outlier.years)]; outlier.years
 [1] 1831 1832 1833 1834 1835 1836 1902 1903 1904 1915 1916 1917 1918 1919 1920 1924 1940 1951 1952 1953
[21] 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973
[41] 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993
[61] 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004

Note that all theoretical outliers occur in in the past 200 years, and the majority in the last 100 years (all years 1951 to the present are theoretical outliers). It doesn’t make sense to remove these values, as we are especially interested in this recent spike and the implications for the near future.

——————————– AVERAGE ANNUAL TEMPERATURES ———————————–

Currently, we have monthly temperatures for each city. Some exploration reveals lots of seasonal volatility:

london.temps <- subset(temps.by.city, City == "London")
delhi.temps <- subset(temps.by.city, City == "Delhi")
boston.temps <- subset(temps.by.city, City == "Boston")
plot(london.temps$AverageTemperature,
     ylab = "Temperature (ĀŗC)",
     main = "London Monthly Temperatures over time")

plot(delhi.temps$AverageTemperature,
     ylab = "Temperature (ĀŗC)",
     main = "Delhi Monthly Temperatures over time")

plot(boston.temps$AverageTemperature,
     ylab = "Temperature (ĀŗC)",
     main = "Boston Monthly Temperatures over time")

To simplify later analysis and remove seasonal cycles shown above, we will find the average annual temperature for each city. Note that by taking this step, we will lose information (for example, about seasonal extremes in temperature that might increase over time). To narrow the scope of analysis, this project will focus on the average annual temperature.

start <- 1750 # First year to investigate
end <- 2004 # Most recent year to investigate
 # Function to get the average annual temperature of a city for one year
getAnnualTemp <- function(df, year) {
    # Function returns the average temperature for a data frame containing monthly temperatures
    # Args: - "df" represents data frame of monthly temperatures. We will pass in a df containing
    #           monthly temperatures for one city.
    #       - "year" represents the year for the average temperature
    # Returns: - average temperature for the given year
    s <- subset(df, grepl(year, df[,1])) # getting subset of data that corresponds to given year
    return(mean(s[,2]))
}
allAnnualTemp <- function(city) {
    # Get the average annual temperatures of a city. A wrapper to apply "getAnnualTemp" function.
    # Args: "city" is a string; one of 3500+ contained in the original data set
    # Returns: vector representing annual temperatures for the given city 
    selection <- subset(temps.by.city, City == city) # representing subset of data specific to city
    years <- seq(from = start, to = end) # years for which we will obtain avg temperature
    data <- sapply(years, getAnnualTemp, df = selection) # getting average temp for each year
    return(data)
}
# Creating a dataframe with the average annual temperature for each major city in the data frame
temps <- data.frame(seq(from = start, to = end), sapply(levels(temps.by.city$City), allAnnualTemp))
names(temps)[1] <- "Year"

Plotting annual, rather than monthly temperatures

d <- temps$Delhi
plot(d, ylab = "Temperature (ĀŗC)", main = "Average Annual Temperature in Delhi")

Notice the volatility is much reduced, and there is a much clearer trend to the data.

Dealing with missing values in annual temperatures.

countNA <- function(v) {
    # Function to calculate the percentage of NAs in a vector.
    # Args  - v represents a vector of z-scores
    #       - threshold represents the cut-off: we'll consider any z-score that has 
    #       an absolute value greater than the threshold to be an outlier
    # Returns - percentage from 0 to 100 indicating proportion of outliers in the vector
    return(100 * length(which(is.na(v))) / length(v)) # determine percentage of elements which are NA
}
na.count <- apply(temps, 2, countNA) # calculating the percentage of NAs for each feature
n <- 30 # threshold percentage for NAs
temps2 <- temps[which(na.count < n)] # Keep columns that have fewer than 30% of data missing

This ā€œcutoffā€ of thirty percent is fairly arbitrary. While some cities have data points from as early as the mid 1700s, most cities do not, and therefore have a signficant number of NAs. There was a choice: look at a smaller pool of cities over a long time period, or look at a larger pool of cities over a shorter time period. I sought to find a balance between the two extremes, by eliminating all cities with more than 30% of values missing. This had the effect of cutting down the pool of cities from ~3500 to ~1500.

Determining average global temperature over the same time period.

# Investigating global average
global.avg2 <- rowMeans(temps2[,2:ncol(temps2)]) # Calculate global average temperature
# 1st column represents year, so we don't include when finding row's mean
temps2 <- cbind(temps2, global.avg2) # add new feature to data frame
index <- which(is.na(temps2$global.avg2)) # determine which rows have NA values
test2 <- temps2[-index,] # Select non-NA values for new data frame

Adding human population and atmospheric information to our data frame

start <- 1750 # First year
end <- 2004 # Last year
Year <- seq(from = start, to = end) # create x values for approximation
recent.humans <- human.pop[5:20,] # human population values from 1750 to 2010
inter <- approx(recent.humans$Year, recent.humans$PopulationInBillions, Year) # interpolating population 
# values for specified years
HumanPopulation <- inter$y # Taking just the dependent variable
recent.gas <- gas.data[start:end,-1] # atmosphere values from 1750 to present
# We exclude the redundant date column
df <- data.frame(Year, recent.gas, HumanPopulation, global.avg2) # Creating data frame 
df <- df[-which(is.na(df$global.avg2)),] # Removing instances where global average is NA

Correlation analysis of feature variables for average global temperature.

cor(df[,-1])
                   CH4spl  CH4_GrRt    NOAA04    CO2spl  CO2_GrRt    N2Ospl  N2O_GrRt
CH4spl          1.0000000 0.5387259 1.0000000 0.9819777 0.8842230 0.9892544 0.5959635
CH4_GrRt        0.5387259 1.0000000 0.5387247 0.3898339 0.5049741 0.4519536 0.4228455
NOAA04          1.0000000 0.5387247 1.0000000 0.9819779 0.8842309 0.9892541 0.5959671
CO2spl          0.9819777 0.3898339 0.9819779 1.0000000 0.8643315 0.9945262 0.5725553
CO2_GrRt        0.8842230 0.5049741 0.8842309 0.8643315 1.0000000 0.8668743 0.7086720
N2Ospl          0.9892544 0.4519536 0.9892541 0.9945262 0.8668743 1.0000000 0.5775884
N2O_GrRt        0.5959635 0.4228455 0.5959671 0.5725553 0.7086720 0.5775884 1.0000000
HumanPopulation 0.9918638 0.4546048 0.9918648 0.9918433 0.8908021 0.9894073 0.5974856
global.avg2     0.7537645 0.2359332 0.7537690 0.7844406 0.5843317 0.7748700 0.3374640
                HumanPopulation global.avg2
CH4spl                0.9918638   0.7537645
CH4_GrRt              0.4546048   0.2359332
NOAA04                0.9918648   0.7537690
CO2spl                0.9918433   0.7844406
CO2_GrRt              0.8908021   0.5843317
N2Ospl                0.9894073   0.7748700
N2O_GrRt              0.5974856   0.3374640
HumanPopulation       1.0000000   0.7598900
global.avg2           0.7598900   1.0000000

We are most interested in variables that are correlated with global.avg2 (the global average temperature). The predictor with the highest correlation is CO2spl, followed closely by HumanPopulation, N2Ospl, NOAA04, and CH4spl. All predictor variables have a correlation > 0.7 with global average, except for the growth rates of CH4, N2O, and CO2.

Investigating collinearity of predictor variables.

cor(df[,-c(1, 3, 6, 8, 10)])
                   CH4spl    NOAA04    CO2spl    N2Ospl HumanPopulation
CH4spl          1.0000000 1.0000000 0.9819777 0.9892544       0.9918638
NOAA04          1.0000000 1.0000000 0.9819779 0.9892541       0.9918648
CO2spl          0.9819777 0.9819779 1.0000000 0.9945262       0.9918433
N2Ospl          0.9892544 0.9892541 0.9945262 1.0000000       0.9894073
HumanPopulation 0.9918638 0.9918648 0.9918433 0.9894073       1.0000000

It is clear that these predictor variables are all highly correlated. Therefore, we should choose one to use in our regression model. Because CO2spl has the highest correlation with global average, we will use CO2spl as the predictive variable in our regression model.

A note on the collinearity: it makes sense that these variables have high correlation. CH4, CO2, and N2O are all pollutants produced simultaneously from machinery such as internal combustion engines and other fossil-fuel burning processes. Humans have burned more fossil fuels as our numbers have increased, so it also makes sense that human population is highly correlated with the concentration of these pollutants. However, the level of correlation surprised me.

Investigating remaining variables:

cor(df[,c(5, 3, 6, 8, 10)])
               CO2spl  CH4_GrRt  CO2_GrRt  N2O_GrRt global.avg2
CO2spl      1.0000000 0.3898339 0.8643315 0.5725553   0.7844406
CH4_GrRt    0.3898339 1.0000000 0.5049741 0.4228455   0.2359332
CO2_GrRt    0.8643315 0.5049741 1.0000000 0.7086720   0.5843317
N2O_GrRt    0.5725553 0.4228455 0.7086720 1.0000000   0.3374640
global.avg2 0.7844406 0.2359332 0.5843317 0.3374640   1.0000000

None of the remaining variables has a correlation above 0.6 with global temperature average. The next highest, CO2_GrRt is close at 0.58, but has a fairly strong correlation with CO2_spl, and therefore doesn’t seem like a good candidate for another variable. N2O and CH4 GrRts have a lower correlation with CO2spl, but a fairly weak correlation with global average. Scatterplots affirm this weak correlation, so we will consider these variables when initially developing our model.

Taking a step back: we’re trying to create a linear model that relates the concentration of gases to the global temperature. The Growth Rate of the gases represents the derivative of the gas concentration with respect to time. Therefore, if the gas concentration correlates well with the global temperature average, then we wouldn’t expect the derivative (growth rate) to correlate as well.

Plotting growth rate against temperature

plot(df$CH4_GrRt, df$global.avg2)

plot(df$N2O_GrRt, df$global.avg2)

Exporting cleaned data to .csv file

write.table(df, 
            sep = ",",
            file = "globalTemp_predictors.csv",
            col.names = FALSE,
            row.names = FALSE)

—————————————- LOCATION EFFECT ———————————- Now, we’ll investigate the effect of a city’s location on it’s temperature. To make this model compatible with the ā€œStage 1ā€ model created above, for our dependent variable we will use mean temperature difference from global average. In Stage 1 we will predict the average global temperature at a future date based on pollutant concentrations, then in Stage 2 we will predict the local temperature for a city relative to the global average, based on its location.

Determining the difference between a city’s average annual temperature and the global average annual temperature.

getDiff <- function(v) {
    # Function to find the mean average between city and global average temperatures
    # Args: v is vector representing city's annual average temperatures
    # Returns: mean difference in temperature between given city and global avg
    return(mean(unlist(v - test2["global.avg2"])))
}
x <- test2[,2:(ncol(test2)-1)] # columns containing cities' annual temperatures
tempDiffs <- apply(x, 2, getDiff) # applying the function to each city (column)

Functions to get the latitude and longitude of a city

getLat <- function(city) {
    i <- match(city, temps.by.city$City)
    return(temps.by.city[i, "Latitude"])
}
getLong <- function(city) {
    # Get the latitude of a given city
    i <- match(city, temps.by.city$City)
    return(temps.by.city[i, "Longitude"])
}

Getting latitude and longitude of cities

city.list <- colnames(test2) # List of city names whose data we'll investigate
city.list <- city.list[2:(length(city.list) - 1)] # First column is year; last column is global avg
city.list <- gsub("\\.", " ", city.list) # replacing periods with spaces in city names
lat <- sapply(city.list, getLat) # Get latitude of each city

Data-wrangling latitudes and longitudes. Latitude is represented as number between 0 and 90, followed by either ā€œNā€ or ā€œSā€. Longitude ranges from 0 to 180, followed by ā€œWā€ or ā€œEā€. I will represent latitude as a number between -90 (representing 90ĀŗS) and 90 (90ĀŗN), and longitude as a number between -180 (180ĀŗW) and 180 (180ĀŗE). Note that 180ĀŗW = 180ĀŗE.

lat <- as.character(lat) # turning latitude to character vector
neg.lat <- which(grepl("S", lat)) # which latitudes are in the southern hemisphere?
EqDist <- substr(lat, start = 1, stop = nchar(lat)-1) # Get numeric value from latitude
EqDist <- as.numeric(EqDist) # converting string to numeric value
# Representing southern hemisphere latitudes with negative number
EqDist[neg.lat] <- EqDist[neg.lat] * -1 
long <- as.character(long) # turning longitude into character vector
neg.long <- which(grepl("W", long)) # which longitudes are in the "West"?
PmDist <- substr(long, start = 1, stop = nchar(long)-1) # Get numeric value from longitude
PmDist <- as.numeric(PmDist) # converting string to numeric value
# Representing Western hemisphere longitudes with negative number
PmDist[neg.long] <- PmDist[neg.long] * -1

Creating data frame to hold geographic location and temperature variance data

geoData <- data.frame(tempDiffs, EqDist, PmDist) 

Investigating variable relationships and correlations

plot(PmDist, EqDist, xlab = "Longitude", ylab = "Latitude", main = "Map") # Observing geographic distribution

plot(EqDist, tempDiffs, 
     xlab = "Latitude", 
     ylab = "Temperature Difference from Global Avg.",
     main = "Temperature v. Latitude")

# Effect of latitude on temperature difference
plot(PmDist, tempDiffs, 
     xlab = "Longitude", 
     ylab = "Temperature Difference from Global Avg.",
     main = "Temperature v. Longitude") # Effect of longitude on temperature difference

cor(geoData) # correlation matrix
           tempDiffs     EqDist     PmDist
tempDiffs  1.0000000 -0.9265409  0.2129410
EqDist    -0.9265409  1.0000000 -0.2568419
PmDist     0.2129410 -0.2568419  1.0000000

Based on plotting, there are two outliers that should be removed (the only two cases from the southern hemisphere). If we had more data points available for the southern hemisphere, we would create two distinct models based on clustering: one linear model for the southern hemisphere and one for the northern hemisphere. Alternatively, we could take the absolute value of the latitude, instead using ā€œdistance from the equatorā€ as our predictive variable. It is important to note that our model only takes into account cities from the Northern Hemisphere, and may not translate to southern hemisphere cities.

There is a strong negative correlation between Latitude and Temperature Difference, and a weak positive correlation between Longitude and Temperature Difference. It makes sense that temperature decreases as distance from the equator increases. It is less obvious how longitude would have an effect on temperature, but cluster analysis could reveal certain regions that have higher or lower than expected temperatures.

Removing outliers

geoData <- geoData[which(geoData$EqDist > 0),] # removing outliers (southern hemisphere cities)

Exporting cleaned data to .csv file

write.table(geoData, 
            sep = ",",
            file = "geoData.csv",
            col.names = FALSE,
            row.names = FALSE)

———————— TEMPERATURE FORECASTING ———————————- Creating forecast for temperature based on prior temperature, using linear Regression trend-line.

First, checking for normality and correlation

delhi.data <- temps[c("Year", "Delhi")]
hist(delhi.data$Delhi) # histogram

plot(delhi.data$Year, delhi.data$Delhi) # Plotting average annual temp over time

NAs <- which(is.na(delhi.data$Delhi)) # Determining location of missing values
a <- delhi.data$Year[-NAs] # "Year" column, excluding any missing values
b <- delhi.data$Delhi[-NAs] # "Temperature" column, excluding any missing values
cor(a,b, method = 'pearson') # determining correlation
[1] 0.5773668

The temperatures appear to be normally distributed and correlated to year, meaning linear regression trend fitting will be suitable method for creating a model.

Identifying outliers

length(which(zScore(delhi.data$Delhi) > 3)) # Count the elements more than 3 standard deviations from the mean
[1] 0

There are no temperatures more than three standard deviations from the mean, so we will not eliminate any cases as outliers.

Removing missing values, and creating data.frame to hold Delhi’s data.

# First, determining proportion of missing values
length(which(is.na(delhi.data$Delhi)))/nrow(delhi.data)
[1] 0.2823529
nrow(delhi.data)
[1] 255
delhi.data <- delhi.data[-which(is.na(delhi.data$Delhi)),] # removing NA values

I choose to remove all NA values, which account for about 28% of the data set. I make this choice because the initial sample size was fairly large (255), and each of the samples consisted of a twelve month average. Furthermore, when creating a linear regression model, I anticipate putting more weight on recent temperatures (because of human activity in the last 100-150 years which has potentially accelerated the growth rate of temperature). Most of the missing values come from earlier years (~1750 - 1850), so would not have played as significant a role as the more recent temperatures will.

Exporting cleaned data to .csv file

write.table(delhi.data, 
            sep = ",",
            file = "delhiTemps.csv",
            col.names = FALSE,
            row.names = FALSE)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKUGFja2FnZXMKYGBge3J9CmxpYnJhcnkoWE1MKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkoeWFtbCkKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoUkN1cmwpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KEJTREEpCmxpYnJhcnkoZm9yZWNhc3QpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoUlNRTGl0ZSkKbGlicmFyeShwc3ljaCkKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBJTVBPUlRJTkcgREFUQSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCkltcG9ydGluZyBnbG9iYWwgdGVtcGVyYXR1cmUgKGJ5IGNpdHkpIGRhdGEgc2V0LiBUaGlzIGRhdGEgc2V0IHdhcyBmb3VuZCBvbiBLYWdnbGU6IGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vYmVya2VsZXllYXJ0aC9jbGltYXRlLWNoYW5nZS1lYXJ0aC1zdXJmYWNlLXRlbXBlcmF0dXJlLWRhdGEKYGBge3J9CnRlbXBzLmJ5LmNpdHkgPC0gcmVhZC5jc3YoIkdsb2JhbExhbmRUZW1wZXJhdHVyZXNCeUNpdHkuY3N2IikKbmFtZXModGVtcHMuYnkuY2l0eSkgPC0gYygiRGF0ZSIsICJBdmVyYWdlVGVtcGVyYXR1cmUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBdmVyYWdlVGVtcGVyYXR1cmVVbmNlcnRhaW50eSIsICJDaXR5IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNvdW50cnkiLCAiTGF0aXR1ZGUiLCAiTG9uZ2l0dWRlIikKYGBgCgoKSW1wb3J0aW5nIGF0bW9zcGhlcmljIGRhdGEgZnJvbSB0aGUgd2ViLiBTb21lIGJhY2tncm91bmQgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGRhdGE6ClNwbGluZSBmaXRzIHRvIHRoZSBMYXcgRG9tZSBmaXJuIGFuZCBpY2UgY29yZSByZWNvcmRzIGFuZCB0aGUgQ2FwZSBHcmltIHJlY29yZC4gCk9jdG9iZXIgMjAwOC4gIFRoZSBzcGxpbmUgZml0cyBmb2xsb3cgRW50aW5nICgxOTg3KSBhbmQgYXR0ZW51YXRlIHZhcmlhdGlvbnMgd2l0aCAKcGVyaW9kcyBvZiBsZXNzIHRoYW4gMjAgeWVhcnMgYnkgNTAlLiAgU2VlIEV0aGVyaWRnZSBldCBhbC4sIEpHUiwgMTk5NjsgCkV0aGVyaWRnZSBldCBhbC4sIEpHUiwgMTk5ODsgTWFjRmFybGluZyBNZXVyZSBldCBhbC4sIEdSTCwgMjAwNjsgCmNvbnRhY3QgZGF2aWQuZXRoZXJpZGdlQGNzaXJvLmF1IAoKQ29sdW1uIDE6IFllYXIgQUQgCkNvbHVtbiAyOiBDSDQgU3BsaW5lIChwcGIpIApDb2x1bW4gMzogR3Jvd3RoIFJhdGUgKHBwYi95cikgCkNvbHVtbiA0OiBOT0FBMDQgc2NhbGUgCkNvbHVtbiA1OiBZZWFyIEFEIApDb2x1bW4gNjogQ08yIFNwbGluZSAocHBtKSAKQ29sdW1uIDc6IEdyb3d0aCBSYXRlIChwcG0veXIpIApDb2x1bW4gODogWWVhciBBRCAKQ29sdW1uIDk6IE4yTyBTcGxpbmUgKHBwYikgCkNvbHVtbiAxMDogR3Jvd3RoIFJhdGUgKHBwbS95cikKCmBgYHtyfQp1cmwgPC0gImZ0cDovL2Z0cC5uY2RjLm5vYWEuZ292L3B1Yi9kYXRhL3BhbGVvL2ljZWNvcmUvYW50YXJjdGljYS9sYXcvbGF3MjAwNi50eHQiCmdhcy5kYXRhIDwtIHJlYWQudGFibGUodXJsLCBza2lwID0gMTgyLCBoZWFkZXIgPSBUUlVFLCBucm93cyA9IDIwMDQpCmdhcy5kYXRhIDwtIGdhcy5kYXRhW2MoLTUsIC04KV0gIyByZW1vdmluZyBkdXBsaWNhdGUgY29sdW1ucwpuYW1lcyhnYXMuZGF0YSkgPC0gYygiWWVhckFEIiwgIkNINHNwbCIsICJDSDRfR3JSdCIsICJOT0FBMDQiLCAiQ08yc3BsIiwgIkNPMl9HclJ0IiwgIk4yT3NwbCIsICJOMk9fR3JSdCIpCmBgYAoKCkltcG9ydGluZyBodW1hbiBwb3B1bGF0aW9uIGRhdGEgZnJvbSBVTiByZXBvcnQgZm91bmQgYXQgaHR0cHM6Ly93d3cudW4ub3JnL2VzYS9wb3B1bGF0aW9uL3B1YmxpY2F0aW9ucy9zaXhiaWxsaW9uL3NpeGJpbHBhcnQxLnBkZi4gRGF0YSBzY3JhcGVkIGZyb20gcGFnZSA1IG9mIHRoZSBQREYgcmVwb3J0LgoKIlRhYnVsYSIgaXMgb3Blbi1zb3VyY2UgYXBwbGljYXRpb24gSSB1c2VkIHRvIHNjcmFwZSBkYXRhIGZyb20gUERGIChodHRwOi8vdGFidWxhLnRlY2hub2xvZ3kvKS4gVGFidWxhIGFwcGxpY2F0aW9uIHNjcmFwZXMgdGhlIFBERiBhbmQgZXhwb3J0cyBkYXRhIGluIC5jc3YgZmlsZSBmb3JtYXQuIEkgd2lsbCBpbXBvcnQgdGhlIC5jc3YgZmlsZSBpbnRvIFIgYWZ0ZXIgc2F2aW5nIHRvIG15IHdvcmtpbmcgZGlyZWN0b3J5LgpgYGB7cn0KaHVtYW4ucG9wIDwtIHJlYWQuY3N2KCJ0YWJ1bGEtc2l4YmlscGFydDEuY3N2IiwgaGVhZGVyID0gVFJVRSwgc2tpcCA9IDEpCm5hbWVzKGh1bWFuLnBvcCkgPC0gYygiWWVhciIsICJQb3B1bGF0aW9uSW5CaWxsaW9ucyIpCmBgYAoKRmlyc3QsIGZ1bmN0aW9ucyB0byBoZWxwIGRldGVybWluZSBvdXRsaWVycyBpbiB0aGUgZGF0YSBzZXRzLgpGdW5jdGlvbnMgdG8gZGV0ZXJtaW5lIFotU2NvcmUgKCMgb2Ygc3RhbmRhcmQgZGV2aWF0aW9ucyBmcm9tIG1lYW4pCmBgYHtyfQojIENyZWF0aW5nIGEgaGVscGVyIGZ1bmN0aW9uIHRvIHBlcmZvcm0gei1zY29yZSBzdGFuZGFyZGl6YXRpb24gb24gYW4gZWxlbWVudCBvZiBhIHZlY3Rvcgp6U2NvcmVfaGVscGVyIDwtIGZ1bmN0aW9uKHgsIHYpIHsKICAgICMgUmVzY2FsZSBhbiBlbGVtZW50ICJ4IiBpbiB2ZWN0b3IgInYiIGJhc2VkIG9uIG51bWJlciBvZiBzdGFuZGFyZCBkZXZpYXRpb25zIGZyb20gdGhlIG1lYW4KICAgICMgQXJnczogeCBpcyBhbiBlbGVtZW50IG9mIGEgbnVtZXJpYyB2ZWN0b3IKICAgICMgICAgICAgdiBpcyBhIG51bWVyaWMgdmVjdG9yCiAgICAjIFJldHVybnM6IGEgdmVjdG9yIGNvbnRhaW5pbmcgdW5ib3VuZGVkIG51bWVyaWMgdmFsdWVzLCBvZiB0aGUgc2FtZSBsZW5ndGggYXMgInYiICAgICAgIAogICAgcmV0dXJuKCh4IC0gbWVhbih2KSkgLyBzZCh2KSkgIyB6LXNjb3JlIHN0YW5kYXJkaXphdGlvbgp9CgojIFdyYXBwZXIgZnVuY3Rpb24gZm9yIHpTY29yZTogdGFrZXMgaW4gYSB2ZWN0b3IgYW5kIHN0YW5kYXJkaXplcyBpdAp6U2NvcmUgPC0gZnVuY3Rpb24odmVjdG9yKSB7CiAgICAjIFJlc2NhbGUgYWxsIGVsZW1lbnRzIG9mIGEgbnVtZXJpYyB2ZWN0b3IgYmFzZWQgb24gdGhlaXIgei1zY29yZQogICAgIyBBcmdzOiB2ZWN0b3IgaXMgYSBudW1lcmljIHZlY3RvcgogICAgIyBSZXR1cm5zOiBudW1lcmljIHZlY3RvciBjb250YWluaW5nIHVuYm91bmRlZCBudW1lcmljIHZhbHVlcywgb2YgdGhlIHNhbWUgbGVuZ3RoIGFzICJ2IgogICAgaWYgKGFueShpcy5uYSh2ZWN0b3IpKSkgeyAjIGlmIHRoZXJlIGFyZSBhbnkgTkFzIGluIHRoZSB2ZWN0b3IKICAgICAgICB2ZWN0b3IgPC0gdmVjdG9yWy13aGljaChpcy5uYSh2ZWN0b3IpKV0gIyByZW1vdmUgYWxsIE5BcwogICAgfQogICAgcmV0dXJuKHNhcHBseSh2ZWN0b3IsIHpTY29yZV9oZWxwZXIsIHYgPSB2ZWN0b3IpKSAjIGRldGVybWluZSB6LXNjb3JlIG9mIGVhY2ggZWxlbWVudCBpbiB2ZWN0b3IKfQpgYGAKCgpEYXRhIGV4cGxvcmF0aW9uIGFuZCB2aXN1YWxpemF0aW9uOgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIEFUTU9TUEhFUklDIEdBUyBDT05DRU5UUkFUSU9OUyAvIEhVTUFOIFBPUFVMQVRJT04gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpBdG1vc3BoZXJpYyBHYXMgY29uY2VudHJhdGlvbnMgb3ZlciB0aW1lOgpgYGB7cn0KcmVjZW50Lmdhcy5kYXRhIDwtIHN1YnNldChnYXMuZGF0YSwgWWVhckFEID4gMTc1MCkKZ2dwbG90KGdhcy5kYXRhLCBhZXMoWWVhckFEKSkgKyAKICBnZW9tX2xpbmUoYWVzKHkgPSBDSDRzcGwsIGNvbG9yID0gIkNINCIpKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gQ08yc3BsLCBjb2xvciA9ICJDTzIiKSkgKwogICAgZ2VvbV9saW5lKGFlcyh5ID0gTjJPc3BsLCBjb2xvciA9ICJOMk8iKSkgKwogICAgbGFicyh4ID0gIlllYXIiLCB5ID0gIkNvbmNlbnRyYXRpb24gKFBQTSkiLCB0aXRsZSA9ICJBdG1vc3BoZXJpYyBHYXMgQ29uY2VudHJhdGlvbnMgc2luY2UgMCBBRCIpCmdncGxvdChnYXMuZGF0YSwgYWVzKFllYXJBRCkpICsgCiAgZ2VvbV9saW5lKGFlcyh5ID0gQ08yc3BsLCBjb2xvciA9ICJDTzIiKSkgKwogICAgZ2VvbV9saW5lKGFlcyh5ID0gTjJPc3BsLCBjb2xvciA9ICJOMk8iKSkgKwogICAgbGFicyh4ID0gIlllYXIiLCB5ID0gIkNvbmNlbnRyYXRpb24gKFBQTSkiLCB0aXRsZSA9ICJBdG1vc3BoZXJpYyBHYXMgQ29uY2VudHJhdGlvbnMgc2luY2UgMCBBRCIpCmdncGxvdChyZWNlbnQuZ2FzLmRhdGEsIGFlcyhZZWFyQUQpKSArIAogIGdlb21fbGluZShhZXMoeSA9IENINHNwbCwgY29sb3IgPSAiQ0g0IikpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBDTzJzcGwsIGNvbG9yID0gIkNPMiIpKSArCiAgICBnZW9tX2xpbmUoYWVzKHkgPSBOMk9zcGwsIGNvbG9yID0gIk4yTyIpKSArCiAgICBsYWJzKHggPSAiWWVhciIsIHkgPSAiQ29uY2VudHJhdGlvbiAoUFBNKSIsIHRpdGxlID0gIkF0bW9zcGhlcmljIEdhcyBDb25jZW50cmF0aW9ucyBzaW5jZSAxNzUwIikKZ2dwbG90KHJlY2VudC5nYXMuZGF0YSwgYWVzKFllYXJBRCkpICsgCiAgZ2VvbV9saW5lKGFlcyh5ID0gQ08yc3BsLCBjb2xvciA9ICJDTzIiKSkgKwogICAgZ2VvbV9saW5lKGFlcyh5ID0gTjJPc3BsLCBjb2xvciA9ICJOMk8iKSkgKwogICAgbGFicyh4ID0gIlllYXIiLCB5ID0gIkNvbmNlbnRyYXRpb24gKFBQTSkiLCB0aXRsZSA9ICJBdG1vc3BoZXJpYyBHYXMgQ29uY2VudHJhdGlvbnMgc2luY2UgMTc1MCIpCmBgYApXZSBub3RlIHRoYXQgdGhlcmUgaXMgYSBkcmFzdGljIGluY3JlYXNlIGluIHRoZSBsZXZlbHMgb2YgZWFjaCBnYXMgaW4gdGVoIGxhc3QgfjI1MCB5ZWFycy4gTGV2ZWxzIG9mIENINCBoYXZlIGluY3JlYXNlZCBtb3JlIHJhcGlkbHkgdGhhbiBDTzIgYW5kIE4yTy4KCgpIdW1hbiBQb3B1bGF0aW9uIG92ZXIgdGltZQpgYGB7cn0KZ2dwbG90KGh1bWFuLnBvcCwgYWVzKFllYXIpKSArIAogIGdlb21fbGluZShhZXMoeSA9IFBvcHVsYXRpb25JbkJpbGxpb25zLCBjb2xvciA9ICJQb3B1bGF0aW9uIGluIEJpbGxpb25zIikpICsKICAgIGxhYnMoeCA9ICJZZWFyIiwgeSA9ICJIdW1hbiBQb3B1bGF0aW9uIChiaWxsaW9ucykiLCB0aXRsZSA9ICJIdW1hbiBQb3B1bGF0aW9uIHNpbmNlIDAgQUQiKQpgYGAKSHVtYW4gcG9wdWxhdGlvbiBoYXMgYWxzbyBza3lyb2NrZXRlZCBpbiByZWNlbnQgeWVhcnMuIFRoZSBncmFwaCBjbG9zZWx5IHJlc2VtYmxlcyB0aGUgYXRtb3NwaGVyaWMgZ2FzIGNvbmNlbnRyYXRpb25zIHBsb3RzIGZyb20gYWJvdmUuCgpIaXN0b2dyYW1zOiBvdXRsaWVyIGRldGVjdGlvbiBhbmQgbm9ybWFsaXR5OgpgYGB7cn0KaGlzdChnYXMuZGF0YSRDSDRzcGwpCmhpc3QoZ2FzLmRhdGEkQ08yc3BsKQpoaXN0KGdhcy5kYXRhJE4yT3NwbCkKaGlzdChnYXMuZGF0YSRDSDRfR3JSdCkKaGlzdChnYXMuZGF0YSRDTzJfR3JSdCkKaGlzdChnYXMuZGF0YSROMk9fR3JSdCkKaGlzdChodW1hbi5wb3AkUG9wdWxhdGlvbkluQmlsbGlvbnMpCmBgYApCYXNlZCBvbiB0aGUgaGlzdG9ncmFtcyBhYm92ZSwgdGhlcmUgYXJlIGxpa2VseSBvdXRsaWVycyBmb3IgZWFjaCBvZiB0aGUgdmFyaWFibGVzIGFib3ZlIChtb3N0IG9mIHRoZSBncmFwaHMgc2tldyB0b3dhcmRzIHRoZSBsZWZ0IHNpZGUpLiBIb3dldmVyLCBkcmF3aW5nIGZyb20gZG9tYWluIGtub3dsZWRnZSwgbWFueSBvZiB0aGUgcmVjZW50IHNwaWtlIGluIGF0bW9zcGhlcmljIGNvbmNlbnRyYXRpb25zIG9mIGdhc2VzIHN1Y2ggYXMgQ08yIGFuZCBOMk8gY2FuIGJlIGF0dHJpYnV0ZWQgdG8gaHVtYW4gYWN0aXZpdHkuIFRoZXJlZm9yZSwgdGhlIHJlY2VudCAobGFzdCB+MjUwIHllYXJzKSBleHBvbmVudGlhbCBncm93dGggaW4gaHVtYW4gcG9wdWxhdGlvbiBjb3VsZCBiZSBhIGNhdXNlIG9mIHRoZSBhcHBhcmVudGx5IGV4cG9uZW50aWFsIHJpc2UgaW4gYXRtb3NwaGVyaWMgZ2FzIGNvbmNlbnRyYXRpb24uIEJlY2F1c2UgdGhlIHB1cnBvc2Ugb2YgdGhpcyBwcm9qZWN0IGlzIGluIHBhcnQgdG8gcXVhbnRpZnkgdGhlIGVmZmVjdHMgb2YgdGhlc2UgaW5jcmVhc2VzLCBJIHdpbGwgdHJ5IHRvIHVzZSBhIGxvZyB0cmFuc2Zvcm0gdG8gYmV0dGVyIG5vcm1hbGl6ZSB0aGUgZGF0YS4KCkhpc3RvZ3JhbXMgb2YgbG9nIHRyYW5zZm9ybXM6CmBgYHtyfQpoaXN0KGxvZyhnYXMuZGF0YSRDSDRzcGwpKQpoaXN0KGxvZyhnYXMuZGF0YSRDTzJzcGwpKQpoaXN0KGxvZyhnYXMuZGF0YSROMk9zcGwpKQpoaXN0KGxvZyhnYXMuZGF0YSRDSDRfR3JSdCkpCmhpc3QobG9nKGdhcy5kYXRhJENPMl9HclJ0KSkKaGlzdChsb2coZ2FzLmRhdGEkTjJPX0dyUnQpKQpoaXN0KGxvZyhodW1hbi5wb3AkUG9wdWxhdGlvbkluQmlsbGlvbnMpKQpgYGAKVGhlIGhpc3RvZ3JhbXMgd2l0aCBsb2cgdHJhbnNmb3JtIGRvIG5vdCBzaG93IGEgbWFya2VkIGluY3JlYXNlIGZyb20gdGhvc2Ugd2l0aG91dCB0aGUgdHJhbnNmb3JtLiBXaGlsZSB0aGUgZGF0YSBkb2VzIG5vdCBhcHBlYXIgdG8gcGVyZmVjdGx5IGZvbGxvdyBhIG5vcm1hbCBkaXN0cmlidXRpb24sIEkgYW0gaGVzaXN0YW50IHRvIGRpc3JlZ2FyZCB0aGUgZXhjZXB0aW9uYWxseSBoaWdoIGNvbmNlbnRyYXRpb25zIGluIHJlY2VudCB5ZWFycyBhcyBvdXRsaWVycy4gVGhlcmUgaXMgYSBjbGVhciB1cHdhcmQgdHJlbmQgb3ZlciB0aW1lLCBhbmQgSSB0aGluayBpdCBpcyBtb3JlIHByYWN0aWNhbCB0byB2aWV3IHRoZXNlIGhpZ2ggcmVhZGluZ3MgYXMgdGhlIHJlc3VsdCBvZiBodW1hbiBhY3Rpdml0eSBpbiByZWNlbnQgeWVhcnMuIAoKV2hpbGUgSSBkbyBub3QgcGxhbiBvbiByZW1vdmluZyBvdXRsaWVycyBiZWNhdXNlIG9mIHRoaXMgcmVhc29uaW5nLCB3ZSB3aWxsIGlkZW50aWZ5IGFsbCBpbnN0YW5jZXMgdGhhdCBmYWxsIGZhcnRoZXIgdGhhbiAzIHN0YW5kYXJkIGRldmlhdGlvbnMgZnJvbSB0aGUgbWVhbiAodGhlb3JldGljYWwgIm91dGxpZXJzIikuCgoKRGV0ZXJtaW5pbmcgaG93IG1hbnkgZWxlbWVudHMgZnJvbSBlYWNoIGZlYXR1cmUgYXJlIHRoZW9yZXRpY2FsIG91dGxpZXJzCmBgYHtyfQpvdXRsaWVyLnllYXJzIDwtIGMod2hpY2goYWJzKHpTY29yZShnYXMuZGF0YSRDSDRzcGwpKSA+IDMpLAogICAgICAgICAgICAgICAgICAgd2hpY2goYWJzKHpTY29yZShnYXMuZGF0YSRDSDRfR3JSdCkpID4gMyksCiAgICAgICAgICAgICAgICAgICB3aGljaChhYnMoelNjb3JlKGdhcy5kYXRhJE4yT3NwbCkpID4gMyksCiAgICAgICAgICAgICAgICAgICB3aGljaChhYnMoelNjb3JlKGdhcy5kYXRhJE5PQUEwNCkpID4gMyksCiAgICAgICAgICAgICAgICAgICB3aGljaChhYnMoelNjb3JlKGdhcy5kYXRhJE4yT19HclJ0KSkgPiAzKSwKICAgICAgICAgICAgICAgICAgIHdoaWNoKGFicyh6U2NvcmUoZ2FzLmRhdGEkQ08yc3BsKSkgPiAzKSwKICAgICAgICAgICAgICAgICAgIHdoaWNoKGFicyh6U2NvcmUoZ2FzLmRhdGEkQ08yX0dyUnQpKSA+IDMpKQpvdXRsaWVyLnllYXJzIDwtIHVuaXF1ZShvdXRsaWVyLnllYXJzKSAjIHVuaW9uIG9mIHNldHMgKGFsbCB5ZWFycyBpbiB3aGljaCBhbiBvdXRsaWVyIG9jY3VycykKb3V0bGllci55ZWFycyA8LSBvdXRsaWVyLnllYXJzW29yZGVyKG91dGxpZXIueWVhcnMpXTsgb3V0bGllci55ZWFycyAjIG9yZGVyaW5nIGNocm9ub2xvZ2ljYWxseQpgYGAKCk5vdGUgdGhhdCBhbGwgdGhlb3JldGljYWwgb3V0bGllcnMgb2NjdXIgaW4gaW4gdGhlIHBhc3QgMjAwIHllYXJzLCBhbmQgdGhlIG1ham9yaXR5IGluIHRoZSBsYXN0IDEwMCB5ZWFycyAoYWxsIHllYXJzIDE5NTEgdG8gdGhlIHByZXNlbnQgYXJlIHRoZW9yZXRpY2FsIG91dGxpZXJzKS4gSXQgZG9lc24ndCBtYWtlIHNlbnNlIHRvIHJlbW92ZSB0aGVzZSB2YWx1ZXMsIGFzIHdlIGFyZSBlc3BlY2lhbGx5IGludGVyZXN0ZWQgaW4gdGhpcyByZWNlbnQgc3Bpa2UgYW5kIHRoZSBpbXBsaWNhdGlvbnMgZm9yIHRoZSBuZWFyIGZ1dHVyZS4KCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBBVkVSQUdFIEFOTlVBTCBURU1QRVJBVFVSRVMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCkN1cnJlbnRseSwgd2UgaGF2ZSBtb250aGx5IHRlbXBlcmF0dXJlcyBmb3IgZWFjaCBjaXR5LiBTb21lIGV4cGxvcmF0aW9uIHJldmVhbHMgbG90cyBvZiBzZWFzb25hbCB2b2xhdGlsaXR5OgpgYGB7cn0KbG9uZG9uLnRlbXBzIDwtIHN1YnNldCh0ZW1wcy5ieS5jaXR5LCBDaXR5ID09ICJMb25kb24iKQpkZWxoaS50ZW1wcyA8LSBzdWJzZXQodGVtcHMuYnkuY2l0eSwgQ2l0eSA9PSAiRGVsaGkiKQpib3N0b24udGVtcHMgPC0gc3Vic2V0KHRlbXBzLmJ5LmNpdHksIENpdHkgPT0gIkJvc3RvbiIpCgpwbG90KGxvbmRvbi50ZW1wcyRBdmVyYWdlVGVtcGVyYXR1cmUsCiAgICAgeWxhYiA9ICJUZW1wZXJhdHVyZSAowrpDKSIsCiAgICAgbWFpbiA9ICJMb25kb24gTW9udGhseSBUZW1wZXJhdHVyZXMgb3ZlciB0aW1lIikKcGxvdChkZWxoaS50ZW1wcyRBdmVyYWdlVGVtcGVyYXR1cmUsCiAgICAgeWxhYiA9ICJUZW1wZXJhdHVyZSAowrpDKSIsCiAgICAgbWFpbiA9ICJEZWxoaSBNb250aGx5IFRlbXBlcmF0dXJlcyBvdmVyIHRpbWUiKQpwbG90KGJvc3Rvbi50ZW1wcyRBdmVyYWdlVGVtcGVyYXR1cmUsCiAgICAgeWxhYiA9ICJUZW1wZXJhdHVyZSAowrpDKSIsCiAgICAgbWFpbiA9ICJCb3N0b24gTW9udGhseSBUZW1wZXJhdHVyZXMgb3ZlciB0aW1lIikKYGBgCgoKVG8gc2ltcGxpZnkgbGF0ZXIgYW5hbHlzaXMgYW5kIHJlbW92ZSBzZWFzb25hbCBjeWNsZXMgc2hvd24gYWJvdmUsIHdlIHdpbGwgZmluZCB0aGUgYXZlcmFnZSBhbm51YWwgdGVtcGVyYXR1cmUgZm9yIGVhY2ggY2l0eS4gTm90ZSB0aGF0IGJ5IHRha2luZyB0aGlzIHN0ZXAsIHdlIHdpbGwgbG9zZSBpbmZvcm1hdGlvbiAoZm9yIGV4YW1wbGUsIGFib3V0IHNlYXNvbmFsIGV4dHJlbWVzIGluIHRlbXBlcmF0dXJlIHRoYXQgbWlnaHQgaW5jcmVhc2Ugb3ZlciB0aW1lKS4gVG8gbmFycm93IHRoZSBzY29wZSBvZiBhbmFseXNpcywgdGhpcyBwcm9qZWN0IHdpbGwgZm9jdXMgb24gdGhlIGF2ZXJhZ2UgYW5udWFsIHRlbXBlcmF0dXJlLgpgYGB7cn0Kc3RhcnQgPC0gMTc1MCAjIEZpcnN0IHllYXIgdG8gaW52ZXN0aWdhdGUKZW5kIDwtIDIwMDQgIyBNb3N0IHJlY2VudCB5ZWFyIHRvIGludmVzdGlnYXRlCgogIyBGdW5jdGlvbiB0byBnZXQgdGhlIGF2ZXJhZ2UgYW5udWFsIHRlbXBlcmF0dXJlIG9mIGEgY2l0eSBmb3Igb25lIHllYXIKZ2V0QW5udWFsVGVtcCA8LSBmdW5jdGlvbihkZiwgeWVhcikgewogICAgIyBGdW5jdGlvbiByZXR1cm5zIHRoZSBhdmVyYWdlIHRlbXBlcmF0dXJlIGZvciBhIGRhdGEgZnJhbWUgY29udGFpbmluZyBtb250aGx5IHRlbXBlcmF0dXJlcwogICAgIyBBcmdzOiAtICJkZiIgcmVwcmVzZW50cyBkYXRhIGZyYW1lIG9mIG1vbnRobHkgdGVtcGVyYXR1cmVzLiBXZSB3aWxsIHBhc3MgaW4gYSBkZiBjb250YWluaW5nCiAgICAjICAgICAgICAgICBtb250aGx5IHRlbXBlcmF0dXJlcyBmb3Igb25lIGNpdHkuCiAgICAjICAgICAgIC0gInllYXIiIHJlcHJlc2VudHMgdGhlIHllYXIgZm9yIHRoZSBhdmVyYWdlIHRlbXBlcmF0dXJlCiAgICAjIFJldHVybnM6IC0gYXZlcmFnZSB0ZW1wZXJhdHVyZSBmb3IgdGhlIGdpdmVuIHllYXIKICAgIHMgPC0gc3Vic2V0KGRmLCBncmVwbCh5ZWFyLCBkZlssMV0pKSAjIGdldHRpbmcgc3Vic2V0IG9mIGRhdGEgdGhhdCBjb3JyZXNwb25kcyB0byBnaXZlbiB5ZWFyCiAgICByZXR1cm4obWVhbihzWywyXSkpCn0KCmFsbEFubnVhbFRlbXAgPC0gZnVuY3Rpb24oY2l0eSkgewogICAgIyBHZXQgdGhlIGF2ZXJhZ2UgYW5udWFsIHRlbXBlcmF0dXJlcyBvZiBhIGNpdHkuIEEgd3JhcHBlciB0byBhcHBseSAiZ2V0QW5udWFsVGVtcCIgZnVuY3Rpb24uCiAgICAjIEFyZ3M6ICJjaXR5IiBpcyBhIHN0cmluZzsgb25lIG9mIDM1MDArIGNvbnRhaW5lZCBpbiB0aGUgb3JpZ2luYWwgZGF0YSBzZXQKICAgICMgUmV0dXJuczogdmVjdG9yIHJlcHJlc2VudGluZyBhbm51YWwgdGVtcGVyYXR1cmVzIGZvciB0aGUgZ2l2ZW4gY2l0eSAKICAgIHNlbGVjdGlvbiA8LSBzdWJzZXQodGVtcHMuYnkuY2l0eSwgQ2l0eSA9PSBjaXR5KSAjIHJlcHJlc2VudGluZyBzdWJzZXQgb2YgZGF0YSBzcGVjaWZpYyB0byBjaXR5CiAgICB5ZWFycyA8LSBzZXEoZnJvbSA9IHN0YXJ0LCB0byA9IGVuZCkgIyB5ZWFycyBmb3Igd2hpY2ggd2Ugd2lsbCBvYnRhaW4gYXZnIHRlbXBlcmF0dXJlCiAgICBkYXRhIDwtIHNhcHBseSh5ZWFycywgZ2V0QW5udWFsVGVtcCwgZGYgPSBzZWxlY3Rpb24pICMgZ2V0dGluZyBhdmVyYWdlIHRlbXAgZm9yIGVhY2ggeWVhcgogICAgcmV0dXJuKGRhdGEpCn0KCiMgQ3JlYXRpbmcgYSBkYXRhZnJhbWUgd2l0aCB0aGUgYXZlcmFnZSBhbm51YWwgdGVtcGVyYXR1cmUgZm9yIGVhY2ggbWFqb3IgY2l0eSBpbiB0aGUgZGF0YSBmcmFtZQp0ZW1wcyA8LSBkYXRhLmZyYW1lKHNlcShmcm9tID0gc3RhcnQsIHRvID0gZW5kKSwgc2FwcGx5KGxldmVscyh0ZW1wcy5ieS5jaXR5JENpdHkpLCBhbGxBbm51YWxUZW1wKSkKbmFtZXModGVtcHMpWzFdIDwtICJZZWFyIgpgYGAKClBsb3R0aW5nIGFubnVhbCwgcmF0aGVyIHRoYW4gbW9udGhseSB0ZW1wZXJhdHVyZXMKYGBge3J9CmQgPC0gdGVtcHMkRGVsaGkKcGxvdChkLCB5bGFiID0gIlRlbXBlcmF0dXJlICjCukMpIiwgbWFpbiA9ICJBdmVyYWdlIEFubnVhbCBUZW1wZXJhdHVyZSBpbiBEZWxoaSIpCmBgYApOb3RpY2UgdGhlIHZvbGF0aWxpdHkgaXMgbXVjaCByZWR1Y2VkLCBhbmQgdGhlcmUgaXMgYSBtdWNoIGNsZWFyZXIgdHJlbmQgdG8gdGhlIGRhdGEuCgoKRGVhbGluZyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluIGFubnVhbCB0ZW1wZXJhdHVyZXMuCmBgYHtyfQpjb3VudE5BIDwtIGZ1bmN0aW9uKHYpIHsKICAgICMgRnVuY3Rpb24gdG8gY2FsY3VsYXRlIHRoZSBwZXJjZW50YWdlIG9mIE5BcyBpbiBhIHZlY3Rvci4KICAgICMgQXJncyAgLSB2IHJlcHJlc2VudHMgYSB2ZWN0b3Igb2Ygei1zY29yZXMKICAgICMgICAgICAgLSB0aHJlc2hvbGQgcmVwcmVzZW50cyB0aGUgY3V0LW9mZjogd2UnbGwgY29uc2lkZXIgYW55IHotc2NvcmUgdGhhdCBoYXMgCiAgICAjICAgICAgIGFuIGFic29sdXRlIHZhbHVlIGdyZWF0ZXIgdGhhbiB0aGUgdGhyZXNob2xkIHRvIGJlIGFuIG91dGxpZXIKICAgICMgUmV0dXJucyAtIHBlcmNlbnRhZ2UgZnJvbSAwIHRvIDEwMCBpbmRpY2F0aW5nIHByb3BvcnRpb24gb2Ygb3V0bGllcnMgaW4gdGhlIHZlY3RvcgogICAgcmV0dXJuKDEwMCAqIGxlbmd0aCh3aGljaChpcy5uYSh2KSkpIC8gbGVuZ3RoKHYpKSAjIGRldGVybWluZSBwZXJjZW50YWdlIG9mIGVsZW1lbnRzIHdoaWNoIGFyZSBOQQp9CgpuYS5jb3VudCA8LSBhcHBseSh0ZW1wcywgMiwgY291bnROQSkgIyBjYWxjdWxhdGluZyB0aGUgcGVyY2VudGFnZSBvZiBOQXMgZm9yIGVhY2ggZmVhdHVyZQoKbiA8LSAzMCAjIHRocmVzaG9sZCBwZXJjZW50YWdlIGZvciBOQXMKdGVtcHMyIDwtIHRlbXBzW3doaWNoKG5hLmNvdW50IDwgbildICMgS2VlcCBjb2x1bW5zIHRoYXQgaGF2ZSBmZXdlciB0aGFuIDMwJSBvZiBkYXRhIG1pc3NpbmcKYGBgClRoaXMgImN1dG9mZiIgb2YgdGhpcnR5IHBlcmNlbnQgaXMgZmFpcmx5IGFyYml0cmFyeS4gV2hpbGUgc29tZSBjaXRpZXMgaGF2ZSBkYXRhIHBvaW50cyBmcm9tIGFzIGVhcmx5IGFzIHRoZSBtaWQgMTcwMHMsIG1vc3QgY2l0aWVzIGRvIG5vdCwgYW5kIHRoZXJlZm9yZSBoYXZlIGEgc2lnbmZpY2FudCBudW1iZXIgb2YgTkFzLiBUaGVyZSB3YXMgYSBjaG9pY2U6IGxvb2sgYXQgYSBzbWFsbGVyIHBvb2wgb2YgY2l0aWVzIG92ZXIgYSBsb25nIHRpbWUgcGVyaW9kLCBvciBsb29rIGF0IGEgbGFyZ2VyIHBvb2wgb2YgY2l0aWVzIG92ZXIgYSBzaG9ydGVyIHRpbWUgcGVyaW9kLiBJIHNvdWdodCB0byBmaW5kIGEgYmFsYW5jZSBiZXR3ZWVuIHRoZSB0d28gZXh0cmVtZXMsIGJ5IGVsaW1pbmF0aW5nIGFsbCBjaXRpZXMgd2l0aCBtb3JlIHRoYW4gMzAlIG9mIHZhbHVlcyBtaXNzaW5nLiBUaGlzIGhhZCB0aGUgZWZmZWN0IG9mIGN1dHRpbmcgZG93biB0aGUgcG9vbCBvZiBjaXRpZXMgZnJvbSB+MzUwMCB0byB+MTUwMC4KCgpEZXRlcm1pbmluZyBhdmVyYWdlIGdsb2JhbCB0ZW1wZXJhdHVyZSBvdmVyIHRoZSBzYW1lIHRpbWUgcGVyaW9kLgpgYGB7cn0KIyBJbnZlc3RpZ2F0aW5nIGdsb2JhbCBhdmVyYWdlCmdsb2JhbC5hdmcyIDwtIHJvd01lYW5zKHRlbXBzMlssMjpuY29sKHRlbXBzMildKSAjIENhbGN1bGF0ZSBnbG9iYWwgYXZlcmFnZSB0ZW1wZXJhdHVyZQojIDFzdCBjb2x1bW4gcmVwcmVzZW50cyB5ZWFyLCBzbyB3ZSBkb24ndCBpbmNsdWRlIHdoZW4gZmluZGluZyByb3cncyBtZWFuCgp0ZW1wczIgPC0gY2JpbmQodGVtcHMyLCBnbG9iYWwuYXZnMikgIyBhZGQgbmV3IGZlYXR1cmUgdG8gZGF0YSBmcmFtZQppbmRleCA8LSB3aGljaChpcy5uYSh0ZW1wczIkZ2xvYmFsLmF2ZzIpKSAjIGRldGVybWluZSB3aGljaCByb3dzIGhhdmUgTkEgdmFsdWVzCnRlc3QyIDwtIHRlbXBzMlstaW5kZXgsXSAjIFNlbGVjdCBub24tTkEgdmFsdWVzIGZvciBuZXcgZGF0YSBmcmFtZQpgYGAKCkFkZGluZyBodW1hbiBwb3B1bGF0aW9uIGFuZCBhdG1vc3BoZXJpYyBpbmZvcm1hdGlvbiB0byBvdXIgZGF0YSBmcmFtZQpgYGB7cn0Kc3RhcnQgPC0gMTc1MCAjIEZpcnN0IHllYXIKZW5kIDwtIDIwMDQgIyBMYXN0IHllYXIKWWVhciA8LSBzZXEoZnJvbSA9IHN0YXJ0LCB0byA9IGVuZCkgIyBjcmVhdGUgeCB2YWx1ZXMgZm9yIGFwcHJveGltYXRpb24KcmVjZW50Lmh1bWFucyA8LSBodW1hbi5wb3BbNToyMCxdICMgaHVtYW4gcG9wdWxhdGlvbiB2YWx1ZXMgZnJvbSAxNzUwIHRvIDIwMTAKaW50ZXIgPC0gYXBwcm94KHJlY2VudC5odW1hbnMkWWVhciwgcmVjZW50Lmh1bWFucyRQb3B1bGF0aW9uSW5CaWxsaW9ucywgWWVhcikgIyBpbnRlcnBvbGF0aW5nIHBvcHVsYXRpb24gCiMgdmFsdWVzIGZvciBzcGVjaWZpZWQgeWVhcnMKSHVtYW5Qb3B1bGF0aW9uIDwtIGludGVyJHkgIyBUYWtpbmcganVzdCB0aGUgZGVwZW5kZW50IHZhcmlhYmxlCnJlY2VudC5nYXMgPC0gZ2FzLmRhdGFbc3RhcnQ6ZW5kLC0xXSAjIGF0bW9zcGhlcmUgdmFsdWVzIGZyb20gMTc1MCB0byBwcmVzZW50CiMgV2UgZXhjbHVkZSB0aGUgcmVkdW5kYW50IGRhdGUgY29sdW1uCgpkZiA8LSBkYXRhLmZyYW1lKFllYXIsIHJlY2VudC5nYXMsIEh1bWFuUG9wdWxhdGlvbiwgZ2xvYmFsLmF2ZzIpICMgQ3JlYXRpbmcgZGF0YSBmcmFtZSAKZGYgPC0gZGZbLXdoaWNoKGlzLm5hKGRmJGdsb2JhbC5hdmcyKSksXSAjIFJlbW92aW5nIGluc3RhbmNlcyB3aGVyZSBnbG9iYWwgYXZlcmFnZSBpcyBOQQpgYGAKCgpDb3JyZWxhdGlvbiBhbmFseXNpcyBvZiBmZWF0dXJlIHZhcmlhYmxlcyBmb3IgYXZlcmFnZSBnbG9iYWwgdGVtcGVyYXR1cmUuCmBgYHtyfQpjb3IoZGZbLC0xXSkKYGBgCldlIGFyZSBtb3N0IGludGVyZXN0ZWQgaW4gdmFyaWFibGVzIHRoYXQgYXJlIGNvcnJlbGF0ZWQgd2l0aCBnbG9iYWwuYXZnMiAodGhlIGdsb2JhbCBhdmVyYWdlIHRlbXBlcmF0dXJlKS4gVGhlIHByZWRpY3RvciB3aXRoIHRoZSBoaWdoZXN0IGNvcnJlbGF0aW9uIGlzIENPMnNwbCwgZm9sbG93ZWQgY2xvc2VseSBieSBIdW1hblBvcHVsYXRpb24sIE4yT3NwbCwgTk9BQTA0LCBhbmQgQ0g0c3BsLiBBbGwgcHJlZGljdG9yIHZhcmlhYmxlcyBoYXZlIGEgY29ycmVsYXRpb24gPiAwLjcgd2l0aCBnbG9iYWwgYXZlcmFnZSwgZXhjZXB0IGZvciB0aGUgZ3Jvd3RoIHJhdGVzIG9mIENINCwgTjJPLCBhbmQgQ08yLgoKSW52ZXN0aWdhdGluZyBjb2xsaW5lYXJpdHkgb2YgcHJlZGljdG9yIHZhcmlhYmxlcy4KYGBge3J9CmNvcihkZlssLWMoMSwgMywgNiwgOCwgMTApXSkKYGBgCgpJdCBpcyBjbGVhciB0aGF0IHRoZXNlIHByZWRpY3RvciB2YXJpYWJsZXMgYXJlIGFsbCBoaWdobHkgY29ycmVsYXRlZC4gVGhlcmVmb3JlLCB3ZSBzaG91bGQgY2hvb3NlIG9uZSB0byB1c2UgaW4gb3VyIHJlZ3Jlc3Npb24gbW9kZWwuIEJlY2F1c2UgQ08yc3BsIGhhcyB0aGUgaGlnaGVzdCBjb3JyZWxhdGlvbiB3aXRoIGdsb2JhbCBhdmVyYWdlLCB3ZSB3aWxsIHVzZSBDTzJzcGwgYXMgdGhlIHByZWRpY3RpdmUgdmFyaWFibGUgaW4gb3VyIHJlZ3Jlc3Npb24gbW9kZWwuCgpBIG5vdGUgb24gdGhlIGNvbGxpbmVhcml0eTogaXQgbWFrZXMgc2Vuc2UgdGhhdCB0aGVzZSB2YXJpYWJsZXMgaGF2ZSBoaWdoIGNvcnJlbGF0aW9uLiBDSDQsIENPMiwgYW5kIE4yTyBhcmUgYWxsIHBvbGx1dGFudHMgcHJvZHVjZWQgc2ltdWx0YW5lb3VzbHkgZnJvbSBtYWNoaW5lcnkgc3VjaCBhcyBpbnRlcm5hbCBjb21idXN0aW9uIGVuZ2luZXMgYW5kIG90aGVyIGZvc3NpbC1mdWVsIGJ1cm5pbmcgcHJvY2Vzc2VzLiBIdW1hbnMgaGF2ZSBidXJuZWQgbW9yZSBmb3NzaWwgZnVlbHMgYXMgb3VyIG51bWJlcnMgaGF2ZSBpbmNyZWFzZWQsIHNvIGl0IGFsc28gbWFrZXMgc2Vuc2UgdGhhdCBodW1hbiBwb3B1bGF0aW9uIGlzIGhpZ2hseSBjb3JyZWxhdGVkIHdpdGggdGhlIGNvbmNlbnRyYXRpb24gb2YgdGhlc2UgcG9sbHV0YW50cy4gSG93ZXZlciwgdGhlIGxldmVsIG9mIGNvcnJlbGF0aW9uIHN1cnByaXNlZCBtZS4KCkludmVzdGlnYXRpbmcgcmVtYWluaW5nIHZhcmlhYmxlczoKYGBge3J9CmNvcihkZlssYyg1LCAzLCA2LCA4LCAxMCldKQpgYGAKCgpOb25lIG9mIHRoZSByZW1haW5pbmcgdmFyaWFibGVzIGhhcyBhIGNvcnJlbGF0aW9uIGFib3ZlIDAuNiB3aXRoIGdsb2JhbCB0ZW1wZXJhdHVyZSBhdmVyYWdlLiBUaGUgbmV4dCBoaWdoZXN0LCBDTzJfR3JSdCBpcyBjbG9zZSBhdCAwLjU4LCBidXQgaGFzIGEgZmFpcmx5IHN0cm9uZyBjb3JyZWxhdGlvbiB3aXRoIENPMl9zcGwsIGFuZCB0aGVyZWZvcmUgZG9lc24ndCBzZWVtIGxpa2UgYSBnb29kIGNhbmRpZGF0ZSBmb3IgYW5vdGhlciB2YXJpYWJsZS4gTjJPIGFuZCBDSDQgR3JSdHMgaGF2ZSBhIGxvd2VyIGNvcnJlbGF0aW9uIHdpdGggQ08yc3BsLCBidXQgYSBmYWlybHkgd2VhayBjb3JyZWxhdGlvbiB3aXRoIGdsb2JhbCBhdmVyYWdlLiBTY2F0dGVycGxvdHMgYWZmaXJtIHRoaXMgd2VhayBjb3JyZWxhdGlvbiwgc28gd2Ugd2lsbCBjb25zaWRlciB0aGVzZSB2YXJpYWJsZXMgd2hlbiBpbml0aWFsbHkgZGV2ZWxvcGluZyBvdXIgbW9kZWwuCgpUYWtpbmcgYSBzdGVwIGJhY2s6IHdlJ3JlIHRyeWluZyB0byBjcmVhdGUgYSBsaW5lYXIgbW9kZWwgdGhhdCByZWxhdGVzIHRoZSBjb25jZW50cmF0aW9uIG9mIGdhc2VzIHRvIHRoZSBnbG9iYWwgdGVtcGVyYXR1cmUuIFRoZSBHcm93dGggUmF0ZSBvZiB0aGUgZ2FzZXMgcmVwcmVzZW50cyB0aGUgZGVyaXZhdGl2ZSBvZiB0aGUgZ2FzIGNvbmNlbnRyYXRpb24gd2l0aCByZXNwZWN0IHRvIHRpbWUuIFRoZXJlZm9yZSwgaWYgdGhlIGdhcyBjb25jZW50cmF0aW9uIGNvcnJlbGF0ZXMgd2VsbCB3aXRoIHRoZSBnbG9iYWwgdGVtcGVyYXR1cmUgYXZlcmFnZSwgdGhlbiB3ZSB3b3VsZG4ndCBleHBlY3QgdGhlIGRlcml2YXRpdmUgKGdyb3d0aCByYXRlKSB0byBjb3JyZWxhdGUgYXMgd2VsbC4gCgpQbG90dGluZyBncm93dGggcmF0ZSBhZ2FpbnN0IHRlbXBlcmF0dXJlCmBgYHtyfQpwbG90KGRmJENINF9HclJ0LCBkZiRnbG9iYWwuYXZnMikKcGxvdChkZiROMk9fR3JSdCwgZGYkZ2xvYmFsLmF2ZzIpCmBgYAoKRXhwb3J0aW5nIGNsZWFuZWQgZGF0YSB0byAuY3N2IGZpbGUKYGBge3J9CndyaXRlLnRhYmxlKGRmLCAKICAgICAgICAgICAgc2VwID0gIiwiLAogICAgICAgICAgICBmaWxlID0gImdsb2JhbFRlbXBfcHJlZGljdG9ycy5jc3YiLAogICAgICAgICAgICBjb2wubmFtZXMgPSBGQUxTRSwKICAgICAgICAgICAgcm93Lm5hbWVzID0gRkFMU0UpCmBgYAoKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIExPQ0FUSU9OIEVGRkVDVCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCk5vdywgd2UnbGwgaW52ZXN0aWdhdGUgdGhlIGVmZmVjdCBvZiBhIGNpdHkncyBsb2NhdGlvbiBvbiBpdCdzIHRlbXBlcmF0dXJlLiBUbyBtYWtlIHRoaXMgbW9kZWwgY29tcGF0aWJsZSB3aXRoIHRoZSAiU3RhZ2UgMSIgbW9kZWwgY3JlYXRlZCBhYm92ZSwgZm9yIG91ciBkZXBlbmRlbnQgdmFyaWFibGUgd2Ugd2lsbCB1c2UgbWVhbiB0ZW1wZXJhdHVyZSBkaWZmZXJlbmNlIGZyb20gZ2xvYmFsIGF2ZXJhZ2UuIEluIFN0YWdlIDEgd2Ugd2lsbCBwcmVkaWN0IHRoZSBhdmVyYWdlIGdsb2JhbCB0ZW1wZXJhdHVyZSBhdCBhIGZ1dHVyZSBkYXRlIGJhc2VkIG9uIHBvbGx1dGFudCBjb25jZW50cmF0aW9ucywgdGhlbiBpbiBTdGFnZSAyIHdlIHdpbGwgcHJlZGljdCB0aGUgbG9jYWwgdGVtcGVyYXR1cmUgZm9yIGEgY2l0eSByZWxhdGl2ZSB0byB0aGUgZ2xvYmFsIGF2ZXJhZ2UsIGJhc2VkIG9uIGl0cyBsb2NhdGlvbi4KCkRldGVybWluaW5nIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gYSBjaXR5J3MgYXZlcmFnZSBhbm51YWwgdGVtcGVyYXR1cmUgYW5kIHRoZSBnbG9iYWwgYXZlcmFnZSBhbm51YWwgdGVtcGVyYXR1cmUuCmBgYHtyfQpnZXREaWZmIDwtIGZ1bmN0aW9uKHYpIHsKICAgICMgRnVuY3Rpb24gdG8gZmluZCB0aGUgbWVhbiBhdmVyYWdlIGJldHdlZW4gY2l0eSBhbmQgZ2xvYmFsIGF2ZXJhZ2UgdGVtcGVyYXR1cmVzCiAgICAjIEFyZ3M6IHYgaXMgdmVjdG9yIHJlcHJlc2VudGluZyBjaXR5J3MgYW5udWFsIGF2ZXJhZ2UgdGVtcGVyYXR1cmVzCiAgICAjIFJldHVybnM6IG1lYW4gZGlmZmVyZW5jZSBpbiB0ZW1wZXJhdHVyZSBiZXR3ZWVuIGdpdmVuIGNpdHkgYW5kIGdsb2JhbCBhdmcKICAgIHJldHVybihtZWFuKHVubGlzdCh2IC0gdGVzdDJbImdsb2JhbC5hdmcyIl0pKSkKfQp4IDwtIHRlc3QyWywyOihuY29sKHRlc3QyKS0xKV0gIyBjb2x1bW5zIGNvbnRhaW5pbmcgY2l0aWVzJyBhbm51YWwgdGVtcGVyYXR1cmVzCnRlbXBEaWZmcyA8LSBhcHBseSh4LCAyLCBnZXREaWZmKSAjIGFwcGx5aW5nIHRoZSBmdW5jdGlvbiB0byBlYWNoIGNpdHkgKGNvbHVtbikKYGBgCgpGdW5jdGlvbnMgdG8gZ2V0IHRoZSBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIG9mIGEgY2l0eQpgYGB7cn0KZ2V0TGF0IDwtIGZ1bmN0aW9uKGNpdHkpIHsKICAgIGkgPC0gbWF0Y2goY2l0eSwgdGVtcHMuYnkuY2l0eSRDaXR5KQogICAgcmV0dXJuKHRlbXBzLmJ5LmNpdHlbaSwgIkxhdGl0dWRlIl0pCn0KZ2V0TG9uZyA8LSBmdW5jdGlvbihjaXR5KSB7CiAgICAjIEdldCB0aGUgbGF0aXR1ZGUgb2YgYSBnaXZlbiBjaXR5CiAgICBpIDwtIG1hdGNoKGNpdHksIHRlbXBzLmJ5LmNpdHkkQ2l0eSkKICAgIHJldHVybih0ZW1wcy5ieS5jaXR5W2ksICJMb25naXR1ZGUiXSkKfQpgYGAKCkdldHRpbmcgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSBvZiBjaXRpZXMKYGBge3J9CmNpdHkubGlzdCA8LSBjb2xuYW1lcyh0ZXN0MikgIyBMaXN0IG9mIGNpdHkgbmFtZXMgd2hvc2UgZGF0YSB3ZSdsbCBpbnZlc3RpZ2F0ZQpjaXR5Lmxpc3QgPC0gY2l0eS5saXN0WzI6KGxlbmd0aChjaXR5Lmxpc3QpIC0gMSldICMgRmlyc3QgY29sdW1uIGlzIHllYXI7IGxhc3QgY29sdW1uIGlzIGdsb2JhbCBhdmcKY2l0eS5saXN0IDwtIGdzdWIoIlxcLiIsICIgIiwgY2l0eS5saXN0KSAjIHJlcGxhY2luZyBwZXJpb2RzIHdpdGggc3BhY2VzIGluIGNpdHkgbmFtZXMKCmxhdCA8LSBzYXBwbHkoY2l0eS5saXN0LCBnZXRMYXQpICMgR2V0IGxhdGl0dWRlIG9mIGVhY2ggY2l0eQpsb25nIDwtIHNhcHBseShjaXR5Lmxpc3QsIGdldExvbmcpICMgR2V0IGxvbmdpdHVkZSBvZiBlYWNoIGNpdHkKYGBgCgpEYXRhLXdyYW5nbGluZyBsYXRpdHVkZXMgYW5kIGxvbmdpdHVkZXMuIExhdGl0dWRlIGlzIHJlcHJlc2VudGVkIGFzIG51bWJlciBiZXR3ZWVuIDAgYW5kIDkwLCBmb2xsb3dlZCBieSBlaXRoZXIgIk4iIG9yICJTIi4gTG9uZ2l0dWRlIHJhbmdlcyBmcm9tIDAgdG8gMTgwLCBmb2xsb3dlZCBieSAiVyIgb3IgIkUiLiBJIHdpbGwgcmVwcmVzZW50IGxhdGl0dWRlIGFzIGEgbnVtYmVyIGJldHdlZW4gLTkwIChyZXByZXNlbnRpbmcgOTDCulMpIGFuZCA5MCAoOTDCuk4pLCBhbmQgbG9uZ2l0dWRlIGFzIGEgbnVtYmVyIGJldHdlZW4gLTE4MCAoMTgwwrpXKSBhbmQgMTgwICgxODDCukUpLiBOb3RlIHRoYXQgMTgwwrpXID0gMTgwwrpFLiAKYGBge3J9CmxhdCA8LSBhcy5jaGFyYWN0ZXIobGF0KSAjIHR1cm5pbmcgbGF0aXR1ZGUgdG8gY2hhcmFjdGVyIHZlY3RvcgpuZWcubGF0IDwtIHdoaWNoKGdyZXBsKCJTIiwgbGF0KSkgIyB3aGljaCBsYXRpdHVkZXMgYXJlIGluIHRoZSBzb3V0aGVybiBoZW1pc3BoZXJlPwoKRXFEaXN0IDwtIHN1YnN0cihsYXQsIHN0YXJ0ID0gMSwgc3RvcCA9IG5jaGFyKGxhdCktMSkgIyBHZXQgbnVtZXJpYyB2YWx1ZSBmcm9tIGxhdGl0dWRlCkVxRGlzdCA8LSBhcy5udW1lcmljKEVxRGlzdCkgIyBjb252ZXJ0aW5nIHN0cmluZyB0byBudW1lcmljIHZhbHVlCgojIFJlcHJlc2VudGluZyBzb3V0aGVybiBoZW1pc3BoZXJlIGxhdGl0dWRlcyB3aXRoIG5lZ2F0aXZlIG51bWJlcgpFcURpc3RbbmVnLmxhdF0gPC0gRXFEaXN0W25lZy5sYXRdICogLTEgCgpsb25nIDwtIGFzLmNoYXJhY3Rlcihsb25nKSAjIHR1cm5pbmcgbG9uZ2l0dWRlIGludG8gY2hhcmFjdGVyIHZlY3RvcgpuZWcubG9uZyA8LSB3aGljaChncmVwbCgiVyIsIGxvbmcpKSAjIHdoaWNoIGxvbmdpdHVkZXMgYXJlIGluIHRoZSAiV2VzdCI/CgpQbURpc3QgPC0gc3Vic3RyKGxvbmcsIHN0YXJ0ID0gMSwgc3RvcCA9IG5jaGFyKGxvbmcpLTEpICMgR2V0IG51bWVyaWMgdmFsdWUgZnJvbSBsb25naXR1ZGUKUG1EaXN0IDwtIGFzLm51bWVyaWMoUG1EaXN0KSAjIGNvbnZlcnRpbmcgc3RyaW5nIHRvIG51bWVyaWMgdmFsdWUKCiMgUmVwcmVzZW50aW5nIFdlc3Rlcm4gaGVtaXNwaGVyZSBsb25naXR1ZGVzIHdpdGggbmVnYXRpdmUgbnVtYmVyClBtRGlzdFtuZWcubG9uZ10gPC0gUG1EaXN0W25lZy5sb25nXSAqIC0xCmBgYAoKQ3JlYXRpbmcgZGF0YSBmcmFtZSB0byBob2xkIGdlb2dyYXBoaWMgbG9jYXRpb24gYW5kIHRlbXBlcmF0dXJlIHZhcmlhbmNlIGRhdGEKYGBge3J9Cmdlb0RhdGEgPC0gZGF0YS5mcmFtZSh0ZW1wRGlmZnMsIEVxRGlzdCwgUG1EaXN0KSAKYGBgCgpJbnZlc3RpZ2F0aW5nIHZhcmlhYmxlIHJlbGF0aW9uc2hpcHMgYW5kIGNvcnJlbGF0aW9ucwpgYGB7cn0KcGxvdChQbURpc3QsIEVxRGlzdCwgeGxhYiA9ICJMb25naXR1ZGUiLCB5bGFiID0gIkxhdGl0dWRlIiwgbWFpbiA9ICJNYXAiKSAjIE9ic2VydmluZyBnZW9ncmFwaGljIGRpc3RyaWJ1dGlvbgpwbG90KEVxRGlzdCwgdGVtcERpZmZzLCAKICAgICB4bGFiID0gIkxhdGl0dWRlIiwgCiAgICAgeWxhYiA9ICJUZW1wZXJhdHVyZSBEaWZmZXJlbmNlIGZyb20gR2xvYmFsIEF2Zy4iLAogICAgIG1haW4gPSAiVGVtcGVyYXR1cmUgdi4gTGF0aXR1ZGUiKQojIEVmZmVjdCBvZiBsYXRpdHVkZSBvbiB0ZW1wZXJhdHVyZSBkaWZmZXJlbmNlCgpwbG90KFBtRGlzdCwgdGVtcERpZmZzLCAKICAgICB4bGFiID0gIkxvbmdpdHVkZSIsIAogICAgIHlsYWIgPSAiVGVtcGVyYXR1cmUgRGlmZmVyZW5jZSBmcm9tIEdsb2JhbCBBdmcuIiwKICAgICBtYWluID0gIlRlbXBlcmF0dXJlIHYuIExvbmdpdHVkZSIpICMgRWZmZWN0IG9mIGxvbmdpdHVkZSBvbiB0ZW1wZXJhdHVyZSBkaWZmZXJlbmNlCmNvcihnZW9EYXRhKSAjIGNvcnJlbGF0aW9uIG1hdHJpeApgYGAKCkJhc2VkIG9uIHBsb3R0aW5nLCB0aGVyZSBhcmUgdHdvIG91dGxpZXJzIHRoYXQgc2hvdWxkIGJlIHJlbW92ZWQgKHRoZSBvbmx5IHR3byBjYXNlcyBmcm9tIHRoZSBzb3V0aGVybiBoZW1pc3BoZXJlKS4gSWYgd2UgaGFkIG1vcmUgZGF0YSBwb2ludHMgYXZhaWxhYmxlIGZvciB0aGUgc291dGhlcm4gaGVtaXNwaGVyZSwgd2Ugd291bGQgY3JlYXRlIHR3byBkaXN0aW5jdCBtb2RlbHMgYmFzZWQgb24gY2x1c3RlcmluZzogb25lIGxpbmVhciBtb2RlbCBmb3IgdGhlIHNvdXRoZXJuIGhlbWlzcGhlcmUgYW5kIG9uZSBmb3IgdGhlIG5vcnRoZXJuIGhlbWlzcGhlcmUuIEFsdGVybmF0aXZlbHksIHdlIGNvdWxkIHRha2UgdGhlIGFic29sdXRlIHZhbHVlIG9mIHRoZSBsYXRpdHVkZSwgaW5zdGVhZCB1c2luZyAiZGlzdGFuY2UgZnJvbSB0aGUgZXF1YXRvciIgYXMgb3VyIHByZWRpY3RpdmUgdmFyaWFibGUuIEl0IGlzIGltcG9ydGFudCB0byBub3RlIHRoYXQgb3VyIG1vZGVsIG9ubHkgdGFrZXMgaW50byBhY2NvdW50IGNpdGllcyBmcm9tIHRoZSBOb3J0aGVybiBIZW1pc3BoZXJlLCBhbmQgbWF5IG5vdCB0cmFuc2xhdGUgdG8gc291dGhlcm4gaGVtaXNwaGVyZSBjaXRpZXMuCgpUaGVyZSBpcyBhIHN0cm9uZyBuZWdhdGl2ZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIExhdGl0dWRlIGFuZCBUZW1wZXJhdHVyZSBEaWZmZXJlbmNlLCBhbmQgYSB3ZWFrIHBvc2l0aXZlIGNvcnJlbGF0aW9uIGJldHdlZW4gTG9uZ2l0dWRlIGFuZCBUZW1wZXJhdHVyZSBEaWZmZXJlbmNlLiBJdCBtYWtlcyBzZW5zZSB0aGF0IHRlbXBlcmF0dXJlIGRlY3JlYXNlcyBhcyBkaXN0YW5jZSBmcm9tIHRoZSBlcXVhdG9yIGluY3JlYXNlcy4gSXQgaXMgbGVzcyBvYnZpb3VzIGhvdyBsb25naXR1ZGUgd291bGQgaGF2ZSBhbiBlZmZlY3Qgb24gdGVtcGVyYXR1cmUsIGJ1dCBjbHVzdGVyIGFuYWx5c2lzIGNvdWxkIHJldmVhbCBjZXJ0YWluIHJlZ2lvbnMgdGhhdCBoYXZlIGhpZ2hlciBvciBsb3dlciB0aGFuIGV4cGVjdGVkIHRlbXBlcmF0dXJlcy4KClJlbW92aW5nIG91dGxpZXJzCmBgYHtyfQpnZW9EYXRhIDwtIGdlb0RhdGFbd2hpY2goZ2VvRGF0YSRFcURpc3QgPiAwKSxdICMgcmVtb3Zpbmcgb3V0bGllcnMgKHNvdXRoZXJuIGhlbWlzcGhlcmUgY2l0aWVzKQpgYGAKCkV4cG9ydGluZyBjbGVhbmVkIGRhdGEgdG8gLmNzdiBmaWxlCmBgYHtyfQp3cml0ZS50YWJsZShnZW9EYXRhLCAKICAgICAgICAgICAgc2VwID0gIiwiLAogICAgICAgICAgICBmaWxlID0gImdlb0RhdGEuY3N2IiwKICAgICAgICAgICAgY29sLm5hbWVzID0gRkFMU0UsCiAgICAgICAgICAgIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gVEVNUEVSQVRVUkUgRk9SRUNBU1RJTkcgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpDcmVhdGluZyBmb3JlY2FzdCBmb3IgdGVtcGVyYXR1cmUgYmFzZWQgb24gcHJpb3IgdGVtcGVyYXR1cmUsIHVzaW5nIGxpbmVhciBSZWdyZXNzaW9uIHRyZW5kLWxpbmUuCgpGaXJzdCwgY2hlY2tpbmcgZm9yIG5vcm1hbGl0eSBhbmQgY29ycmVsYXRpb24KYGBge3J9CmRlbGhpLmRhdGEgPC0gdGVtcHNbYygiWWVhciIsICJEZWxoaSIpXQpoaXN0KGRlbGhpLmRhdGEkRGVsaGkpICMgaGlzdG9ncmFtCnBsb3QoZGVsaGkuZGF0YSRZZWFyLCBkZWxoaS5kYXRhJERlbGhpKSAjIFBsb3R0aW5nIGF2ZXJhZ2UgYW5udWFsIHRlbXAgb3ZlciB0aW1lCgpOQXMgPC0gd2hpY2goaXMubmEoZGVsaGkuZGF0YSREZWxoaSkpICMgRGV0ZXJtaW5pbmcgbG9jYXRpb24gb2YgbWlzc2luZyB2YWx1ZXMKYSA8LSBkZWxoaS5kYXRhJFllYXJbLU5Bc10gIyAiWWVhciIgY29sdW1uLCBleGNsdWRpbmcgYW55IG1pc3NpbmcgdmFsdWVzCmIgPC0gZGVsaGkuZGF0YSREZWxoaVstTkFzXSAjICJUZW1wZXJhdHVyZSIgY29sdW1uLCBleGNsdWRpbmcgYW55IG1pc3NpbmcgdmFsdWVzCmNvcihhLGIsIG1ldGhvZCA9ICdwZWFyc29uJykgIyBkZXRlcm1pbmluZyBjb3JyZWxhdGlvbgpgYGAKVGhlIHRlbXBlcmF0dXJlcyBhcHBlYXIgdG8gYmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgYW5kIGNvcnJlbGF0ZWQgdG8geWVhciwgbWVhbmluZyBsaW5lYXIgcmVncmVzc2lvbiB0cmVuZCBmaXR0aW5nIHdpbGwgYmUgc3VpdGFibGUgbWV0aG9kIGZvciBjcmVhdGluZyBhIG1vZGVsLgoKSWRlbnRpZnlpbmcgb3V0bGllcnMKYGBge3J9Cmxlbmd0aCh3aGljaCh6U2NvcmUoZGVsaGkuZGF0YSREZWxoaSkgPiAzKSkgIyBDb3VudCB0aGUgZWxlbWVudHMgbW9yZSB0aGFuIDMgc3RhbmRhcmQgZGV2aWF0aW9ucyBmcm9tIHRoZSBtZWFuCmBgYApUaGVyZSBhcmUgbm8gdGVtcGVyYXR1cmVzIG1vcmUgdGhhbiB0aHJlZSBzdGFuZGFyZCBkZXZpYXRpb25zIGZyb20gdGhlIG1lYW4sIHNvIHdlIHdpbGwgbm90IGVsaW1pbmF0ZSBhbnkgY2FzZXMgYXMgb3V0bGllcnMuCgpSZW1vdmluZyBtaXNzaW5nIHZhbHVlcywgYW5kIGNyZWF0aW5nIGRhdGEuZnJhbWUgdG8gaG9sZCBEZWxoaSdzIGRhdGEuCmBgYHtyfQojIEZpcnN0LCBkZXRlcm1pbmluZyBwcm9wb3J0aW9uIG9mIG1pc3NpbmcgdmFsdWVzCmxlbmd0aCh3aGljaChpcy5uYShkZWxoaS5kYXRhJERlbGhpKSkpL25yb3coZGVsaGkuZGF0YSkKbnJvdyhkZWxoaS5kYXRhKQpkZWxoaS5kYXRhIDwtIGRlbGhpLmRhdGFbLXdoaWNoKGlzLm5hKGRlbGhpLmRhdGEkRGVsaGkpKSxdICMgcmVtb3ZpbmcgTkEgdmFsdWVzCmBgYApJIGNob29zZSB0byByZW1vdmUgYWxsIE5BIHZhbHVlcywgd2hpY2ggYWNjb3VudCBmb3IgYWJvdXQgMjglIG9mIHRoZSBkYXRhIHNldC4gSSBtYWtlIHRoaXMgY2hvaWNlIGJlY2F1c2UgdGhlIGluaXRpYWwgc2FtcGxlIHNpemUgd2FzIGZhaXJseSBsYXJnZSAoMjU1KSwgYW5kIGVhY2ggb2YgdGhlIHNhbXBsZXMgY29uc2lzdGVkIG9mIGEgdHdlbHZlIG1vbnRoIGF2ZXJhZ2UuIEZ1cnRoZXJtb3JlLCB3aGVuIGNyZWF0aW5nIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwsIEkgYW50aWNpcGF0ZSBwdXR0aW5nIG1vcmUgd2VpZ2h0IG9uIHJlY2VudCB0ZW1wZXJhdHVyZXMgKGJlY2F1c2Ugb2YgaHVtYW4gYWN0aXZpdHkgaW4gdGhlIGxhc3QgMTAwLTE1MCB5ZWFycyB3aGljaCBoYXMgcG90ZW50aWFsbHkgYWNjZWxlcmF0ZWQgdGhlIGdyb3d0aCByYXRlIG9mIHRlbXBlcmF0dXJlKS4gTW9zdCBvZiB0aGUgbWlzc2luZyB2YWx1ZXMgY29tZSBmcm9tIGVhcmxpZXIgeWVhcnMgKH4xNzUwIC0gMTg1MCksIHNvIHdvdWxkIG5vdCBoYXZlIHBsYXllZCBhcyBzaWduaWZpY2FudCBhIHJvbGUgYXMgdGhlIG1vcmUgcmVjZW50IHRlbXBlcmF0dXJlcyB3aWxsLgoKCkV4cG9ydGluZyBjbGVhbmVkIGRhdGEgdG8gLmNzdiBmaWxlCmBgYHtyfQp3cml0ZS50YWJsZShkZWxoaS5kYXRhLCAKICAgICAgICAgICAgc2VwID0gIiwiLAogICAgICAgICAgICBmaWxlID0gImRlbGhpVGVtcHMuY3N2IiwKICAgICAgICAgICAgY29sLm5hbWVzID0gRkFMU0UsCiAgICAgICAgICAgIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKCgo=